﻿using System;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MugenInjection;
using MugenInjection.Activators;
using MugenInjection.Bindings;
using MugenInjection.Bindings.Builders;
using MugenInjection.Core;
using MugenInjection.Exceptions;
using MugenInjection.Infrastructure;
using MugenInjection.Interface;
using MugenInjection.Syntax;
using MugenInjection.Syntax.Constant;
using TestModels.Class;
using TestModels.Interface;
using Should;

namespace MugenInjectionTest.Bindings
{
    public class SimpleAttribute : Attribute
    {

    }

    [SimpleAttribute]
    public class ClassHasAttribute : IEmpty
    {

    }

    [TestClass]
    public class BindingBuilderConstantTest : TestBase
    {
        [TestMethod]
        public void CreateBindingTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty), typeof(I1) }, injector);
                IBindingSyntax binding = bindingBuilder;
                binding.ToConstant(new SimpleClass());

                var constantBinding = (ConstantBinding)bindingBuilder.Build();
                constantBinding.ShouldNotBeNull();
                constantBinding.Services.Contains(typeof(IEmpty)).ShouldBeTrue();
                constantBinding.Services.Contains(typeof(I1)).ShouldBeTrue();
                constantBinding.ActivatingActions.ShouldBeEmpty();
                constantBinding.ActivatedActions.ShouldBeEmpty();
                constantBinding.DeactivatedActions.ShouldBeEmpty();
                constantBinding.TryDisposeObject.ShouldBeFalse();
                constantBinding.Parameters.ShouldBeEmpty();
                constantBinding.Priority.ShouldEqual(BindingPriority.Standard);
                constantBinding.ScopeLifecycle.ReleaseObjectActions.ShouldBeEmpty();
                
                Action action = () => new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty), typeof(IEmpty) }, injector);
                action.ShouldThrow<ArgumentException>();
            }
        }

        [TestMethod]
        public void WithConditionWhenIntoTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                IBindingSyntax binding = bindingBuilder;
                IConstantObjectPriorityWhenSyntax syntax = binding.ToConstant(new SimpleClass());
                syntax.WhenInto(typeof(MemberInfo));

                Action action = () => syntax.WhenInto(typeof(object));
                action.ShouldThrow<InvalidBindingException>();

                var constantBinding = (ConstantBinding)bindingBuilder.Build();
                constantBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(IEmpty).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeFalse();

                constantBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(PropertyInfo).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeFalse();

                constantBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(MemberInfo).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeTrue();
                constantBinding.Priority.ShouldEqual(BindingPriority.Low);
            }
        }

        [TestMethod]
        public void WithConditionWhenIntoGenericTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                IBindingSyntax bindingSyntax = bindingBuilder;
                IConstantObjectPriorityWhenSyntax syntax = bindingSyntax.ToConstant(new SimpleClass());
                syntax.WhenInto<MemberInfo>();

                Action action = () => syntax.WhenInto(typeof(object));
                action.ShouldThrow<InvalidBindingException>();

                var constantBinding = (ConstantBinding)bindingBuilder.Build();
                constantBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(IEmpty).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeFalse();

                constantBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(PropertyInfo).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeFalse();

                constantBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(MemberInfo).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeTrue();
                constantBinding.Priority.ShouldEqual(BindingPriority.Low);
            }
        }

        [TestMethod]
        public void WithConditionWhenIntoIsAssignableTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                IBindingSyntax binding = bindingBuilder;
                IConstantObjectPriorityWhenSyntax syntax = binding.ToConstant(new SimpleClass());
                syntax.WhenIntoIsAssignable(typeof(MemberInfo));

                Action action = () => syntax.WhenIntoIsAssignable(typeof(object));
                action.ShouldThrow<InvalidBindingException>();

                var constantBinding = (ConstantBinding)bindingBuilder.Build();
                constantBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(IEmpty).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeFalse();

                constantBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(PropertyInfo).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeTrue();

                constantBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(MemberInfo).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeTrue();
                constantBinding.Priority.ShouldEqual(BindingPriority.Low);
            }
        }

        [TestMethod]
        public void WithConditionWhenIntoIsAssignableGenericTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                IBindingSyntax binding = bindingBuilder;
                IConstantObjectPriorityWhenSyntax syntax = binding.ToConstant(new SimpleClass());
                syntax.WhenIntoIsAssignable<MemberInfo>();

                Action action = () => syntax.WhenIntoIsAssignable(typeof(object));
                action.ShouldThrow<InvalidBindingException>();

                var constantBinding = (ConstantBinding)bindingBuilder.Build();
                constantBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(IEmpty).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeFalse();

                constantBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(PropertyInfo).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeTrue();

                constantBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(MemberInfo).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeTrue();
                constantBinding.Priority.ShouldEqual(BindingPriority.Low);
            }
        }

        [TestMethod]
        public void WithConditionWhenNamespaceEqualTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                IBindingSyntax binding = bindingBuilder;
                IConstantObjectPriorityWhenSyntax syntax = binding.ToConstant(new SimpleClass());
                syntax.WhenNamespaceEqual("System");

                Action action = () => syntax.WhenNamespaceEqual("System");
                action.ShouldThrow<InvalidBindingException>();

                var constantBinding = (ConstantBinding)bindingBuilder.Build();
                constantBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(IEmpty).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeFalse();

                constantBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(object).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeTrue();
                constantBinding.Priority.ShouldEqual(BindingPriority.Low);
            }
        }

        [TestMethod]
        public void WithConditionWhenClassHasAttributeTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                IBindingSyntax binding = bindingBuilder;
                IConstantObjectPriorityWhenSyntax syntax = binding.ToConstant(new SimpleClass());
                syntax.WhenClassHasAttribute(typeof(SimpleAttribute));

                Action action = () => syntax.WhenClassHasAttribute(typeof(SimpleAttribute));
                action.ShouldThrow<InvalidBindingException>();

                var constantBinding = (ConstantBinding)bindingBuilder.Build();
                constantBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(IEmpty).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeFalse();

                constantBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(PropertyInfo).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeFalse();

                constantBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(ClassHasAttribute).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeTrue();
                constantBinding.Priority.ShouldEqual(BindingPriority.Low);
            }
        }

        [TestMethod]
        public void WithConditionWhenClassHasAttributeGenericTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                IBindingSyntax binding = bindingBuilder;
                IConstantObjectPriorityWhenSyntax syntax = binding.ToConstant(new SimpleClass());
                syntax.WhenClassHasAttribute<SimpleAttribute>();

                Action action = () => syntax.WhenClassHasAttribute(typeof(SimpleAttribute));
                action.ShouldThrow<InvalidBindingException>();

                var constantBinding = (ConstantBinding)bindingBuilder.Build();
                constantBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(IEmpty).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeFalse();

                constantBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(PropertyInfo).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeFalse();

                constantBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(ClassHasAttribute).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeTrue();
                constantBinding.Priority.ShouldEqual(BindingPriority.Low);
            }
        }

        [TestMethod]
        public void WithConditionWhenTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                IBindingSyntax binding = bindingBuilder;
                IConstantObjectPriorityWhenSyntax syntax = binding.ToConstant(new SimpleClass());
                syntax.When(context => context.TypeInto == typeof(object));

                Action action = () => syntax.When(context => context.TypeInto == typeof(object));
                action.ShouldThrow<InvalidBindingException>();

                var constantBinding = (ConstantBinding)bindingBuilder.Build();
                constantBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(IEmpty).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeFalse();

                constantBinding.CanResolve(new BindingContext(typeof(IEmpty), typeof(object).GetTypeInfo(), null,
                                                              injector, injector,
                                                              InjectorUtilsInternal.EmptyInjectionParams,
                                                              InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                               .ShouldBeTrue();
                constantBinding.Priority.ShouldEqual(BindingPriority.Normal);
            }
        }

        [TestMethod]
        public void PriorityTest()
        {
            using (var injector = GetInjector())
            {
                //Test priority
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                IBindingSyntax binding = bindingBuilder;
                binding.ToConstant(new SimpleClass()).SetStandardPriority();

                var constantBinding = (ConstantBinding)bindingBuilder.Build();
                constantBinding.Priority.ShouldEqual(BindingPriority.Standard);

                //Low
                bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                binding = bindingBuilder;
                binding.ToConstant(new SimpleClass()).SetLowPriority();

                constantBinding = (ConstantBinding)bindingBuilder.Build();
                constantBinding.Priority.ShouldEqual(BindingPriority.Low);

                //Normal
                bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                binding = bindingBuilder;
                binding.ToConstant(new SimpleClass()).SetNormalPriority();

                constantBinding = (ConstantBinding)bindingBuilder.Build();
                constantBinding.Priority.ShouldEqual(BindingPriority.Normal);

                //High
                bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                binding = bindingBuilder;
                binding.ToConstant(new SimpleClass()).SetHighPriority();

                constantBinding = (ConstantBinding)bindingBuilder.Build();
                constantBinding.Priority.ShouldEqual(BindingPriority.High);

                //Custom
                bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) }, injector);
                binding = bindingBuilder;
                IConstantObjectPriorityWhenSyntax syntax = binding.ToConstant(new SimpleClass());
                syntax.SetPriority(BindingPriority.High);

                constantBinding = (ConstantBinding)bindingBuilder.Build();
                constantBinding.Priority.ShouldEqual(BindingPriority.High);

                Action action = () => syntax.SetNormalPriority();
                action.ShouldThrow<InvalidBindingException>();
            }
        }

        [TestMethod]
        public void NamedBindingTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) },
                                                                                        injector);
                IBindingSyntax binding = bindingBuilder;

                var syntax = binding.ToConstant(new SimpleClass()).NamedBinding("NamedBinding");
                Action action = () => syntax.NamedBinding("Test");
                action.ShouldThrow<InvalidBindingException>();

                var constantBinding = (ConstantBinding)bindingBuilder.Build();
                var dictionary = new Dictionary<string, object>();
                var bindingContext = new BindingContext(typeof(IEmpty), typeof(IEmpty).GetTypeInfo(), null, injector, injector, InjectorUtilsInternal.EmptyInjectionParams, dictionary);
                constantBinding.CanResolve(bindingContext).ShouldBeFalse();
                SpecialParameterKeys.SetKeyedBindingParameter(dictionary, "NamedBinding");
                constantBinding.CanResolve(bindingContext).ShouldBeTrue();
            }
        }

        [TestMethod]
        public void KeyedBindingTest()
        {
            var key = new object();
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) },
                                                                                        injector);
                IBindingSyntax binding = bindingBuilder;

                var syntax = binding.ToConstant(new SimpleClass()).KeyedBinding(key);
                Action action = () => syntax.KeyedBinding(key);
                action.ShouldThrow<InvalidBindingException>();

                var constantBinding = (ConstantBinding)bindingBuilder.Build();
                var dictionary = new Dictionary<string, object>();
                var bindingContext = new BindingContext(typeof(IEmpty), typeof(IEmpty).GetTypeInfo(), null, injector, injector, InjectorUtilsInternal.EmptyInjectionParams, dictionary);
                constantBinding.CanResolve(bindingContext).ShouldBeFalse();
                SpecialParameterKeys.SetKeyedBindingParameter(dictionary, key);
                constantBinding.CanResolve(bindingContext).ShouldBeTrue();
            }
        }

        [TestMethod]
        public void NamedBindingWithConditionTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) },
                                                                                        injector);
                IBindingSyntax binding = bindingBuilder;

                var syntax = binding.ToConstant(new SimpleClass()).WhenInto<SimpleClass>().NamedBinding("NamedBinding");
                Action action = () => syntax.NamedBinding("Test");
                action.ShouldThrow<InvalidBindingException>();

                var constantBinding = (ConstantBinding)bindingBuilder.Build();
                var dictionary = new Dictionary<string, object>();
                var bindingContext = new BindingContext(typeof(IEmpty), typeof(IEmpty).GetTypeInfo(), null, injector, injector, InjectorUtilsInternal.EmptyInjectionParams, dictionary);
                constantBinding.CanResolve(bindingContext).ShouldBeFalse();
                SpecialParameterKeys.SetKeyedBindingParameter(dictionary, "NamedBinding");
                constantBinding.CanResolve(bindingContext).ShouldBeFalse();

                bindingContext = new BindingContext(typeof(IEmpty), typeof(SimpleClass).GetTypeInfo(), null, injector, injector, InjectorUtilsInternal.EmptyInjectionParams, dictionary);
                constantBinding.CanResolve(bindingContext).ShouldBeTrue();
            }
        }

        [TestMethod]
        public void TryDisposeObjectTest()
        {
            using (var injector = GetInjector())
            {
                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) },
                                                                                        injector);
                IBindingSyntax binding = bindingBuilder;
                var syntax = binding.ToConstant(new SimpleClass()).TryDisposeObjects();
                Action action = () => syntax.TryDisposeObjects();
                action.ShouldThrow<InvalidBindingException>();

                var constantBinding = (ConstantBinding)bindingBuilder.Build();
                constantBinding.TryDisposeObject.ShouldBeTrue();
            }
        }

        [TestMethod]
        public void WithSettingTest()
        {
            using (var injector = GetInjector())
            {
                var settingObj = new object();
                var settingObj1 = new object();

                var bindingBuilder = new BindingBuilder<object, object, object, object>(new[] { typeof(IEmpty) },
                                                                                        injector);
                IBindingSyntax binding = bindingBuilder;
                binding.ToConstant(new SimpleClass()).WithSetting("test", settingObj).WithSetting("test1", settingObj1);

                var constantBinding = (ConstantBinding)bindingBuilder.Build();
                constantBinding.Settings.Get("test").ShouldEqual(settingObj);
                constantBinding.Settings.Get("test1").ShouldEqual(settingObj1);
            }
        }

        [TestMethod]
        public void BindingBuilderTest()
        {
            using (var injector = GetInjector())
            {
                bool isBuilded = false;
                bool isBuilding = false;
                var bindingBuilder = new BindingBuilder<I1, object, object, object>(new[] { typeof(I1) }, injector).ToConstant(new Class());

                bindingBuilder.Building += builder =>
                {
                    isBuilded.ShouldBeFalse();
                    builder.ShouldNotBeNull();
                    isBuilding = true;
                    return builder;
                };
                bindingBuilder.Builded += builder =>
                {
                    var binding = builder.Build();
                    isBuilded = true;
                    isBuilding.ShouldBeTrue();
                    binding.ShouldBeType<ConstantBinding>();
                    return binding;
                };

                bindingBuilder.Services.Contains(typeof(I1)).ShouldBeTrue();
                bindingBuilder.Injector.ShouldEqual(injector);

                (bindingBuilder.Build() is ConstantBinding).ShouldBeTrue();
                isBuilded.ShouldBeTrue();
            }
        }
    }
}